/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package metier.session;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import javax.annotation.Resource;
import javax.ejb.Local;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.Cache;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import metier.entity.Client;
import metier.entity.Compte;
import metier.entity.Virement;
import tools.cryptage.FacadeBCrypt;
import metier.tools.Filter;
import tools.cryptage.ICryptage;
import tools.cryptage.IOTPEngine;
import tools.cryptage.TestOTPEngine;

/**
 *
 * @author Adrien
 */
@Stateless
@Local
public class ClientManager implements IClientManager {

    @PersistenceContext
    EntityManager em;
    
    private final ICryptage cryptage    = new FacadeBCrypt ();
    private final IOTPEngine cryptageOtp = new TestOTPEngine();
    
    @Override
    public void addClient(String nom, String prenom, String email, String ville){
        Client unclient = new Client();
        unclient.setNom(nom);
        unclient.setPrenom(prenom);
        unclient.setEmail(email);
        unclient.setEtat(Client.ACTIVE);
        unclient.setVille(ville);
        em.persist(unclient);
        Cache cache = em.getEntityManagerFactory().getCache();
        cache.evict(Client.class);
    }
    
    @Override
    public List<metier.pojo.Client> getClients(){
        Query q = em.createQuery("FROM Client c WHERE c.etat != "+ Client.DESACTIVE,Client.class);
        List<Client> uneliste = q.getResultList();
        return convert(uneliste);
    }
    
    @Override
    public void desactiverClient(long id){
        Client leclient = em.find(Client.class, id);
        leclient.setEtat(Client.DESACTIVE);
        em.persist(leclient);
    }
    
    @Override
    public void activerClient(long id){
        Client leclient = em.find(Client.class, id);
        leclient.setEtat(Client.ACTIVE);
        em.persist(leclient);
    }
    
    
    // Add business logic below. (Right-click in editor and choose
    // "Insert Code > Add Business Method")

    @Override
    public metier.pojo.Client findClient(long id) {
        Client unclient = em.find(Client.class, id);
        metier.pojo.Client retour_client = null;
        if (unclient != null)
        {
            retour_client = convert(unclient);
        }
        return retour_client;
    }
    
    public metier.pojo.Client findClientWithAccount(long id) {
        Client unclient = em.find(Client.class, id);
        metier.pojo.Client retour_client = null;
        if (unclient != null)
        {
            retour_client = convert(unclient);
            retour_client.setComptes(this.getComptes(unclient,retour_client));
        }
        return retour_client;
    }

    @Override
    public List<metier.pojo.Client> findClientByNomAndPrenom(String mc) {
        Query q = em.createQuery ("FROM Client c WHERE c.nom like :nom OR c.prenom like :prenom AND c.etat != :etat",Client.class);
        q.setParameter("nom", mc + "%");
        q.setParameter("prenom", mc + "%");
        q.setParameter("etat", Client.DESACTIVE);
        List<Client> uneliste = q.getResultList();
        
        return convert (uneliste);
    }
    
    @Override
    public List<metier.pojo.Client> findClientByVille(String ville) {
        Query q = em.createQuery ("FROM Client c WHERE c.ville like :ville AND c.etat != :etat",Client.class);
        q.setParameter("ville", ville + "%");
        q.setParameter("etat", Client.DESACTIVE);
        List<Client> uneliste = q.getResultList();
        
        return convert (uneliste);
    }
    
    public List<metier.pojo.Client> findClientByFilter(List<Filter> filtres) {
        String query = "FROM Client c WHERE c.etat != :etat";
        int cpt = 0;
        for (Filter unfiltre:filtres)
        {
            String mc = "like";
            if (unfiltre.getMode() == Filter.MODE_EQUL)
            {
                mc = "=";
            }
            
            query += " AND c." + unfiltre.getColonne() + " " + mc + " " + ":value" + cpt;
            cpt++;
        }
        Query q = em.createQuery (query,Client.class);
        cpt = 0;
        for (Filter unfiltre:filtres){
            String value = unfiltre.getValue();
            if (unfiltre.getMode() == Filter.MODE_LIKE_DEB)
            {
                value += "%";
            }
            
            q.setParameter("value" + cpt, value);
            cpt++;
        }
        
        
        q.setParameter("etat", Client.DESACTIVE);
        List<Client> uneliste = q.getResultList();
        
        return convert (uneliste);
    }
    
    public List<metier.pojo.Compte> getComptes (long id_utilisateur)
    {
        List<metier.pojo.Compte> comptes = new ArrayList<metier.pojo.Compte> ();
        metier.pojo.Compte new_compte = null;
        Client unclient = em.find(Client.class, id_utilisateur);
        metier.pojo.Client client_pojo = new metier.pojo.Client(unclient);
        List<Compte> liste = unclient.getPossesseur();
        for (Compte uncompte :liste)
        {
            new_compte = convert(uncompte);
            new_compte.setPossesseur(client_pojo);
            comptes.add(new_compte);
        }
        return comptes;
    }
    
    private List<metier.pojo.Compte> getComptes (Client unclient, metier.pojo.Client client_pojo)
    {
        List<metier.pojo.Compte> comptes = new ArrayList<metier.pojo.Compte> ();
        metier.pojo.Compte new_compte    = null;
        List<Compte> liste = unclient.getPossesseur();
        for (Compte uncompte :liste)
        {
            new_compte = convert(uncompte);
            new_compte.setPossesseur(client_pojo);
            comptes.add(new_compte);
        }
        Collections.sort(comptes, new Comparator<metier.pojo.Compte>(){

            @Override
            public int compare(metier.pojo.Compte o1, metier.pojo.Compte o2) {
                return  o1.getId().compareTo(o2.getId());
            }
        });
        
        return comptes;
    }
    
    public void addMoneyCompte(long id, float montant){
        Compte c = em.find(Compte.class, id);
        c.setMontant(c.getMontant() + montant);
        em.persist(c);
        Cache cache = em.getEntityManagerFactory().getCache();
        cache.evict(Client.class);
    }
    
    /**
     * Cette méthode permet de désactiver le compte d'identifiant "id"
     * @param id le numéro de compte à supprimer
     * @return un code indiquant l'état de la transaction (0 -> success, 1 -> Compte non vide, 2 -> Problème serveur)
     */
    public int delCompte(long id)
    {
        int code = 0;
        Compte c = em.find(Compte.class, id);
        if (c!= null )
        {
            if (c.getMontant() == 0)
            {
                c.setEtat(Compte.COMPTE_DISABLED);
                em.persist(c);
                Cache cache = em.getEntityManagerFactory().getCache();
                cache.evict(Client.class);
                cache.evict(Compte.class);
            }else
            {
                code = 1;
            }
        }
        else
        {
            code = 2;
        }
        return code;
    }
    
    private List<metier.pojo.Client> convert (List<Client> uneliste){
        List<metier.pojo.Client> resultat = new ArrayList<metier.pojo.Client> ();
        for (Client c : uneliste) {
            resultat.add(convert(c));
        }
        return resultat;
    }
    
    public  void addCompte(long id_possesseur, String nom, float montant)
    {
        Client unclient = em.find(Client.class, id_possesseur);
        Compte uncompte = new Compte ();
        uncompte.setNom(nom);
        uncompte.setPossesseur(unclient);
        uncompte.setMontant(montant);
        em.persist(uncompte);
        
        Cache cache = em.getEntityManagerFactory().getCache();
        cache.evict(Client.class);
    }
    
    @Resource SessionContext context;
    
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public boolean transfert(long id_debiteur, long id_crediteur, float Montant)
    {
        boolean success = true;
        try
        {
            Virement unvirement = new Virement ();
            unvirement.setMontant(Montant);
            Compte debiteur  = em.find(Compte.class, id_debiteur);
            
            Compte crediteur = em.find(Compte.class, id_crediteur);
            
            if (debiteur == null || crediteur == null)
                throw new Exception ("Le débiteur et le créditeur n'existe pas");
            
            if (debiteur.getEtat() != Compte.COMPTE_ENABLED || crediteur.getEtat() != Compte.COMPTE_ENABLED)
                throw new Exception ("Un transfert ne peut pas avoir lieu si un des comptes est inactif");
            
            if (Montant <= 0)
                throw new Exception ("Un transfert ne peut pas être négative");
            
            if (crediteur.getMontant() - Montant <= -2000)
                throw new Exception ("Le créditeur a un découvert trop important pour autoriser un transfert");
            
            unvirement.setCrediteur(crediteur);
            unvirement.setDebiteur(debiteur);
            unvirement.setDate_maj(new Timestamp(new Date().getTime()));
            em.persist(unvirement);
            debiteur.setMontant(debiteur.getMontant() + Montant);
            crediteur.setMontant(crediteur.getMontant() - Montant);
            em.persist(debiteur);
            em.persist(crediteur);
            Cache cache = em.getEntityManagerFactory().getCache();
            cache.evict(Client.class);
            cache.evict(Compte.class);
        }
        catch (Exception e)
        {
            context.setRollbackOnly();
            success = false;
        }
        return success;
    }
    
    public metier.pojo.Compte getCompteWithVirementsAndPossesseur(long id_utilisateur, long id_compte)
    {
        Compte uncompte = em.find(Compte.class, id_compte);
        if (uncompte == null)
            return null;
        
        if (uncompte.getPossesseur().getId() != id_utilisateur)
            return null;
        
        metier.pojo.Compte pojo_compte = new metier.pojo.Compte(uncompte);
        pojo_compte.setPossesseur(new metier.pojo.Client(uncompte.getPossesseur()));
        
        List<metier.pojo.Virement> virements = new ArrayList<metier.pojo.Virement> ();
        
        for (Virement unvirement : uncompte.getDebits())
        {
            metier.pojo.Virement v = new metier.pojo.Virement(unvirement);
            metier.pojo.Compte   new_compte = new metier.pojo.Compte (unvirement.getCrediteur());
            new_compte.setPossesseur(new metier.pojo.Client(unvirement.getCrediteur().getPossesseur()));
            
            v.setDebiteur(pojo_compte);
            v.setCrediteur(new_compte);
           
            virements.add(v);
        }
        
        for (Virement unvirement : uncompte.getCredits())
        {
            metier.pojo.Virement v = new metier.pojo.Virement(unvirement);
            metier.pojo.Compte   new_compte = new metier.pojo.Compte (unvirement.getDebiteur());
            new_compte.setPossesseur(new metier.pojo.Client(unvirement.getDebiteur().getPossesseur()));
            v.setDebiteur(new_compte);
            v.setCrediteur(pojo_compte);
            
            virements.add(v);
        }
        Collections.sort(virements, new Comparator(){

            @Override
            public int compare(Object o1, Object o2) {
                if(o1.getClass() == o2.getClass() && o1.getClass() == metier.pojo.Virement.class)
                {
                    metier.pojo.Virement v1  = (metier.pojo.Virement) o1, v2 = (metier.pojo.Virement) o2;
                    return v1.getDate_maj().compareTo(v2.getDate_maj());
                }
                return -1;
            }
        
        });
        
        pojo_compte.setVirements(virements);
        
        return pojo_compte;
    }
    
    @Override
    public void setPassword()
    {
        Query q = em.createQuery("FROM Client c");
        List<Client> clients = q.getResultList();
        
        for (Client unclient:clients)
        {
            String salt = salt();
            unclient.setSalt(salt);
            unclient.setMotpasse(this.cryptage.cryptage("azerty", salt));
            em.persist(unclient);
        }
    }
    
    @Override
    public metier.pojo.Client testPassword(String email, String password, Integer password_otp)
    {
        metier.pojo.Client leclient = null;
        Query q = em.createQuery("FROM Client c WHERE c.email = :email");
        q.setParameter("email", email);
        List<Client> clients = q.getResultList();
        if (!clients.isEmpty())
        {
            Client courant = clients.get(0);
            //password_otp.equals(cryptageOtp.nextOTP(courant.getFirst_otp(), courant.getFirst_otp())
            System.out.println (password);
            System.out.println (courant.getMotpasse());
            if (cryptage.valider(password, courant.getSalt(), courant.getMotpasse()))
            {
                leclient = convert (courant);
            }
        }
        return leclient;
    }
    
    public static byte[] hash(String password)
    {
        MessageDigest sha256;
        byte[] hash = null;
        try {
            sha256 = MessageDigest.getInstance("SHA-512");
            byte[] passBytes = password.getBytes();
            byte[] passHash = sha256.digest(passBytes);
            hash = passHash;
        } catch (NoSuchAlgorithmException ex) 
        {
            
        }
        return hash;
    }
    
    public String salt()
    {
        return cryptage.gensalt();
    }
    
    private metier.pojo.Client convert (Client unclient){
        return new metier.pojo.Client(unclient);
    }
    
    private metier.pojo.Compte convert (Compte uncompte){
        return new metier.pojo.Compte(uncompte);
    }
}
